package com.roadmap.biz.message;

import java.lang.reflect.Field;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;

import org.apache.log4j.Logger;
import org.hibernate.Hibernate;

import com.roadmap.cache.RdcSessionCache;
import com.roadmap.common.BaseConst;
import com.roadmap.common.BaseEntity;
import com.roadmap.common.RdcConst;
import com.roadmap.common.RdcLogger;
import com.roadmap.common.RdcResourceBundle;
import com.roadmap.common.BaseEnum.ActionType;
import com.roadmap.entities.Aspirant;
import com.roadmap.entities.Notice;
import com.roadmap.entities.Request;
import com.roadmap.entities.ext.NoticeExt;
import com.roadmap.exception.InvalidActionException;
import com.roadmap.exception.InvalidQueryException;
import com.roadmap.exception.InvalidValueException;
import com.roadmap.exception.NullObjectException;
import com.roadmap.exception.ServerException;
import com.roadmap.repo.db.DatabaseUtility;
import com.roadmap.repo.db.IDatabaseUtility;
import com.roadmap.repo.db.RdcQuery;

public class NoticeManager {
	private final static Logger logger = RdcLogger
			.getLogger(NoticeManager.class);
	private final IDatabaseUtility dbUtil;
	/**
	 * when aspirant updates his data, his activity will be recorded
	 * automatically, but not all entities he updates
	 */
	private final static Hashtable<String, String> entitiesCanbeRecorded = new Hashtable<String, String>();
	private final static Hashtable<ActionType, String> actionNames = new Hashtable<ActionType, String>();
	/** whose activities should be recorded */
	private String aspirantId;
	// private static boolean isNotConfigured = true;
	private Aspirant aspirant;

	private final static int PERMISSION_INVALID_VALUE = -1;

	static {
		entitiesCanbeRecorded.put(RdcConst.USER,
				RdcResourceBundle.LOCAL_NAME_ASPIRANT);
		entitiesCanbeRecorded.put(RdcConst.ACHIEVE,
				RdcResourceBundle.LOCAL_NAME_ACHIEVEMENT);
		entitiesCanbeRecorded.put(RdcConst.SCORE,
				RdcResourceBundle.LOCAL_NAME_ASSEESSMENT);
		entitiesCanbeRecorded.put(RdcConst.EXEC,
				RdcResourceBundle.LOCAL_NAME_EXEUCTION);
		entitiesCanbeRecorded.put(RdcConst.DREAM,
				RdcResourceBundle.LOCAL_NAME_LIFEMAP);
		entitiesCanbeRecorded.put(RdcConst.GOAL,
				RdcResourceBundle.LOCAL_NAME_LIFEGOAL);
		entitiesCanbeRecorded.put(RdcConst.TASK,
				RdcResourceBundle.LOCAL_NAME_LIFETASK);
		entitiesCanbeRecorded.put(RdcConst.CAREER,
				RdcResourceBundle.LOCAL_NAME_PROFILE_CAREER);
		entitiesCanbeRecorded.put(RdcConst.STUDENT,
				RdcResourceBundle.LOCAL_NAME_PROFILE_STUDENT);
		entitiesCanbeRecorded.put(RdcConst.BLOG,
				RdcResourceBundle.LOCAL_NAME_ARTICLE);
		entitiesCanbeRecorded.put(RdcConst.FRIEND,
				RdcResourceBundle.LOCAL_NAME_FRIEND);
		entitiesCanbeRecorded.put(RdcConst.COMMENT,
				RdcResourceBundle.LOCAL_NAME_COMMENT);

		actionNames.put(ActionType.CREATE, RdcResourceBundle.ACTION_CREATE);
		actionNames.put(ActionType.UPDATE, RdcResourceBundle.ACTION_UPDATE);
		actionNames.put(ActionType.DELETE, RdcResourceBundle.ACTION_DELETE);
		actionNames.put(ActionType.COMPLETE, RdcResourceBundle.ACTION_COMPLETE);
		actionNames.put(ActionType.BECOME, RdcResourceBundle.ACTION_BECOME);
		actionNames.put(ActionType.ACCEPT, RdcResourceBundle.ACTION_ACCEPT);
		actionNames.put(ActionType.COMMENT, RdcResourceBundle.ACTION_COMMENT);
	}

	public static Hashtable<String, String> getEntitiesCanbeRecorded() {
		return entitiesCanbeRecorded;
	}

	public NoticeManager(String aspirantId) {
		this.dbUtil = new DatabaseUtility();
		this.aspirantId = aspirantId;
	}

	public NoticeManager(Aspirant aspirant) {
		this.dbUtil = new DatabaseUtility();
		this.aspirant = aspirant;
		this.aspirantId = aspirant.getId();
	}

	public NoticeManager(String aspirantId, IDatabaseUtility dbUtil) {
		this.aspirantId = aspirantId;
		this.dbUtil = dbUtil;
	}

	public Aspirant getAspirant() throws NullObjectException,
			InvalidQueryException, ServerException, InvalidValueException {
		if (aspirant == null || !aspirant.getId().equals(aspirantId)) {
			aspirant = (Aspirant) dbUtil.getEntityById(Aspirant.class,
					aspirantId);
		}
		return aspirant;
	}

	public IDatabaseUtility getDbUtil() {
		return dbUtil;
	}

	public String getAspirantId() {
		return aspirantId;
	}

	public void setAspirantId(String aspirantId) {
		this.aspirantId = aspirantId;
	}

	/**
	 * create a notice for an entry in order to notify people.
	 * 
	 * @param: toWho - aspirant id; if NULL, notify all people
	 */
	public void notifyForEntry(BaseEntity entity, ActionType type, String toWho)
			throws NullObjectException, ServerException, InvalidActionException {
		if (entity == null || aspirantId == null || type == null) {
			NullObjectException noe = new NullObjectException(
					"invalid inputs. they can't be NULL");
			logger.error(noe);
			throw noe;
		}

		// check if the entity and type should filtered
		if (filter(entity, type)) {
			return;
		}

		// create a notice
		Notice notice = new Notice();
		notice.setDispatcherId(aspirantId);
		notice.setEntryType(entity.getClass().getSimpleName());
		notice.setEntryId(entity.getId());
		notice.setEntryDesc(entity.getWhatItIs());
		notice.setActivity(actionNames.get(type));
		notice.setRecipientId(toWho);
		// if the entry is constraint, get its visibility
		int visibility = getVisibility(entity);
		if (getVisibility(entity) != PERMISSION_INVALID_VALUE) {
			notice.setVisibility(visibility);
		}

		getDbUtil().save(notice);
	}

	/**
	 * 通知非特定人
	 * 
	 */
	public void notifyForEntry(BaseEntity entity, ActionType type)
			throws NullObjectException, ServerException, InvalidActionException {
		notifyForEntry(entity, type, null);
	}

	/**
	 * this method filter some entities and action types if the entities and
	 * types don't satisfy the rules for generating a activity record
	 */
	private boolean filter(BaseEntity entity, ActionType type) {
		return (entitiesCanbeRecorded.containsKey(entity.getClass()
				.getSimpleName()) == false
				|| type == ActionType.UPDATE || type == ActionType.DELETE);
	}

	/** only some entries can be set access permissions */
	private int getVisibility(BaseEntity entry) {
		if (entry != null) {
			if (entry instanceof Request) {
				return BaseConst.ENTRY_VISIBILITY_PRIVATE;
			}
			else {
				try {
					Field field = entry.getClass().getDeclaredField(
							BaseConst.PERMISSION_FIELD_NAME);
					if (field != null) {
						return field.getInt(entry);
					}
				}
				catch (Exception e) {
					// not necessary to catch the exception
					if (logger.isDebugEnabled()) {
						logger.debug("the entry "
								+ entry.getClass().getSimpleName()
								+ " has no permission field.");
					}
				}
			}
		}
		return PERMISSION_INVALID_VALUE;
	}

	public List<NoticeExt> getOwnActivities() throws ServerException,
			NullObjectException, InvalidQueryException, InvalidValueException {
		String sql = "select {n.*}, a.realName from (select nt.* from notice as nt where nt.dispatcherId=:me) as n left join aspirant as a on n.recipientId=a.aspirantId order by n.createdTime DESC";
		RdcQuery query = getDbUtil().createNativeSQLQuery(sql);
		query.addEntity("n", Notice.class);
		query.setParameter("me", getAspirantId());
		query.addScalar("realName", Hibernate.STRING);
		List results = query.getResults();
		if (results == null || results.isEmpty()) {
			return null;
		}
		List<NoticeExt> notices = null;
		if (results != null && !results.isEmpty()) {
			notices = new ArrayList<NoticeExt>();
			for (int i = 0; i < results.size(); i++) {
				Object[] result = (Object[]) results.get(i);
				Notice note = (Notice) result[0];
				NoticeExt ext = new NoticeExt(note);
				ext.setRecipientName((String) result[1]);
				ext.setDispatcherName(getAspirant().getRealName());
				ext
						.setEntryType(entitiesCanbeRecorded.get(note
								.getEntryType()));
				notices.add(ext);
			}
		}
		return notices;
	}

	public List getFriendActivities() throws ServerException {
		// filter the friends' IDs
		String[] friends = getFriendIDs();
		if (friends == null || friends.length == 0) {
			return null;
		}

		// get the activities
		String sql = "select {rs.*}, rs.dispatcherName, asp.realName from (select n.*, a.realName as dispatcherName from notice as n, aspirant as a where n.dispatcherId in (:friends) and n.visibility<>:visible and n.dispatcherId=a.aspirantId) as rs left join aspirant as asp on rs.recipientId=asp.aspirantId order by rs.createdTime DESC";
		RdcQuery query = getDbUtil().createNativeSQLQuery(sql);
		query.addEntity("rs", Notice.class);
		query.setParameterList("friends", friends);
		query.setParameter("visible", new Integer(
				BaseConst.ENTRY_VISIBILITY_PRIVATE));
		query.addScalar("dispatcherName", Hibernate.STRING);
		query.addScalar("realName", Hibernate.STRING);
		query.setMaxResults(BaseConst.NOTICE_MAX_RESULTS);
		List results = query.getResults();
		List<NoticeExt> notices = null;
		if (results != null && !results.isEmpty()) {
			notices = new ArrayList<NoticeExt>();
			for (int i = 0; i < results.size(); i++) {
				Object[] result = (Object[]) results.get(i);
				Notice note = (Notice) result[0];
				NoticeExt ext = new NoticeExt(note);
				ext.setDispatcherName((String) result[1]);
				ext.setRecipientName((String) result[2]);
				ext
						.setEntryType(entitiesCanbeRecorded.get(note
								.getEntryType()));
				notices.add(ext);
			}
		}
		return notices;
	}

	/** get the IDs of user's friends */
	private String[] getFriendIDs() throws ServerException {
		// find out the user's friends
		String sql = "select friendAId, friendBId from friendship where friendAId=:uid || friendBId=:uid";
		RdcQuery query = getDbUtil().createNativeSQLQuery(sql);
		query.setParameter("uid", getAspirantId());
		query.addScalar("friendAId", Hibernate.STRING);
		query.addScalar("friendBId", Hibernate.STRING);
		List results = query.getResults();
		if (results == null || results.isEmpty()) {
			return null;
		}

		// filter the friends' IDs
		String[] friends = new String[results.size()];
		for (int i = 0; i < results.size(); i++) {
			Object[] result = (Object[]) results.get(i);
			friends[i] = (!getAspirantId().equals((String) result[0])) ? (String) result[0]
					: (String) result[1];
		}

		return friends;
	}

	/**
	 * get the notices which user's friends have done to the user. load those
	 * new notices
	 * 
	 */
	public List getNoticesFromFriends() throws ServerException,
			NullObjectException, InvalidQueryException, InvalidValueException {
		String sql = "select {n.*}, a.realName from notice as n, aspirant as a where n.recipientId=:uid and (n.status=:status || (n.status<>:status and (TO_DAYS(NOW()) - TO_DAYS(n.createdTime)) <=:days)) and n.dispatcherId=a.aspirantId order by n.createdTime DESC";
		RdcQuery query = getDbUtil().createNativeSQLQuery(sql);
		query.addEntity("n", Notice.class);
		// query.setParameterList("friends", friends);
		query.setParameter("uid", getAspirantId());
		query.setParameter("status", BaseConst.NOTICE_STATUS_UNREAD);
		query.setParameter("days", BaseConst.NOTICE_OLD_DATE_DIFF);
		query.addScalar("realName", Hibernate.STRING);
		query.setMaxResults(BaseConst.NOTICE_MAX_RESULTS);
		List results = query.getResults();
		List<NoticeExt> notices = null;
		if (results != null && !results.isEmpty()) {
			notices = new ArrayList<NoticeExt>();
			for (int i = 0; i < results.size(); i++) {
				Object[] result = (Object[]) results.get(i);
				Notice note = (Notice) result[0];
				NoticeExt ext = new NoticeExt(note);
				ext.setDispatcherName((String) result[1]);
				notices.add(ext);
			}
		}

		// change the new notices to READ
		if (notices != null && !notices.isEmpty()) {
			String hql = "update Notice set status=? where recipientId=? and status=?";
			Object[] params = { BaseConst.NOTICE_STATUS_READ, getAspirantId(),
					BaseConst.NOTICE_STATUS_UNREAD };
			getDbUtil().executeHQLUpdate(hql, params);
		}
		return notices;
	}

	/**
	 * return the amounts of new notices, new requests and new messages.
	 * 
	 * @throws ServerException
	 */
	public void getAmountsOfNewInformation(RdcSessionCache amount)
			throws ServerException {
		if (amount == null) {
			amount = new RdcSessionCache();
		}
		// amount of new notices
		String sql = "select count(*) from notice where recipientId=:uid and status=:state";
		amount.setNoticeCount(getCountOfNewInfo(sql, getAspirantId(),
				BaseConst.NOTICE_STATUS_UNREAD));
		// amount of new requests
		sql = "select count(*) from request where recipientId=:uid and status=:state";
		amount.setRequestCount(getCountOfNewInfo(sql, getAspirantId(),
				BaseConst.REQUEST_STATUS_NEW));
		// amount of new messages
		sql = "select count(*) from dialog as d, message as m where (sponsorId=:uid || participatorId=:uid) and d.dialogId=m.dialogId and m.dispatcherId<>:uid and status=:state";
		amount.setMessageCount(getCountOfNewInfo(sql, getAspirantId(),
				BaseConst.MESSAGE_STATUS_UNREAD));
	}

	private int getCountOfNewInfo(String sql, String uid, int status)
			throws ServerException {
		RdcQuery query = getDbUtil().createNativeSQLQuery(sql);
		query.setParameter("uid", uid);
		query.setParameter("state", status);
		Object count = query.getUniqueResult();
		if (count != null) {
			return ((BigInteger) count).intValue();
		}
		return 0;
	}
}
